/* -*- C++ -*- */
#ifndef __draw_VertexBuffer_Header_File__
#define __draw_VertexBuffer_Header_File__

#include "common.h"



namespace core
{

  namespace draw 
  {
    
    /**
     * Class providing storage of vertices on drawing device
     * and also providing drawing of mesh.
     * 
     * Maximal per vertex structure looks like this:
     * <ol>
     * 	<li>Normal - Vector3f</li>
     *  <li>Color - Vector4f</li>
     *  <li>SecColor - Vector4f</li>
     *  <li>Fog Coord</li>
     * 	<li>TexCoord #1 - Vector2f</li>
     * 	<li>TexCoord #2 - Vector2f</li>
     * 	<li>...</li>
     * 	<li>TexCoord #8 - Vector2f</li>
     *  <li>Vertex - Vector3f</li>
     * 
     * </ol>
     * 
     * To form Type of per vertex structure, you may use constants
     * Vertex, Normal, TexCoord, TexMask, TexShift constants. For example
     * for per vertex stores (normal, N tex coordinate set and vertex) the
     * type describing this format has this format:
     *
     * Format = Normal | (n << TexShift)
     *
     * Where n is count of 2D texture coordintes.
     *
     * This class provides abstract storage of vertices used mainly for
     * drawing of meshes. This class provides methods for:
     * <ul>
     *  <li> Uploading new vertcies data - methods lock(), unlock()</li>
     *  <li> for drawing vertices as mesh - methods draw(), drawIndexed() </li>
     * </ul>
     *
     * @note Currently providen drawing is cappable only of single texture 
     * drawing.
     */
    class VertexBuffer
    {
    public:


      /**
       * Per vertex data format. To form format of per vertex data use this 
       * simple calculation:
       *
       * format = Normal | (n << TexShift), where n is count of 2D texture
       * coordinates.
       *
       * @note You should use combination of these enum values to specify 
       * per vertex data format. This specification is a number:
       * T4 T3 T2 T1  F S C N, where:
       * <ul>
       *  <li>N - is normal bit</li>
       *  <li>C - is color bit</li>
       *  <li>S - is secondary color bit</li>
       *  <li>F - is fog coordinate bit</li>
       *  <li>T1..T4 - are bits for count of textures (0..8 ie 0000..1000 binary)
       * </ul>
       */
      enum Type	{	
	Vertex = 0x00,
	Normal = 0x01,
	Color = 0x02,
	SecColor = 0x04,
	FogCoord = 0x08,
	TexCoord = 0x10,
	TexMask = 0xf0,
	TexShift = 4,
	TexCoordMask = 0xf0,
	TexCoordShift = 4
      };

			

			

      /**
       * Creates new vertex buffer for size vertices, 
       * storing per vertex data in type format.
       * 
       * @param size Count of vertices to be stored.
       * @param type Format of per vertex data
       */
      VertexBuffer(u32 size, Type type);


      /**
       * Frees vertex buffer
       */
      virtual ~VertexBuffer();

      /**
       * Locks current buffer and provides poiner to 
       * internal data, where you can store your vertex
       * data.
       * 
       * @return Pointer to internal data, you can only
       * write to this pointer.
       *
       * You are supposed not to read data from 
       * returned pointer. The only legal operation on
       * returned data is writing (not reading), however
       * this method MAY returned pointer from where you
       * can pontentionaly read data stored within this
       * VertexBuffer object. </p>
       * <p>(In case when VBO OpenGL Extension is not used.
       * For storeage are used OpenGL vertex arrays instead.)</p>
       *.
       * @note <p>This method is used to store (only!) new 
       * vertices data for particalar vertex buffer. 
       * Once particular VertexBuffer object is locked,
       * you cannot call lock method again until the
       * unlock method is called.</p>
       * <p>This method may raise core::InvalidOperationException
       * in case where lock method is called on already
       * locked VertexBuffer object, or in case where
       * OpenGL error is detected.</p>
       * <p>Scenario of using lock()/unlock() methods on 
       * particular VertexBuffer object (vb) is like this:
       * <ol>
       *  <li>void* buf = vb->lock();</li>
       *  <li>memcpy(buf, data, nVert * stride);</li>
       *  <li>vb->unlock();
       * </ol>
       * </p>
       *
       * @note You do not need to bind this buffer, before
       * calling lock method.
       */
      void* lock();

      /**
       * Unlocks current buffer.
       *
       * @note: This method may raise 
       * core::InvalidOperationException in case where
       * unlock method is called on not locked VertexBuffer
       * object, or in case where OpenGL error is detected.
       */
      void unlock();

      /**
       * Binds current vertex buffer to drawing device.
       */
      void bind();

      /**
       * Unbinds current buffer.
       */
      void unbind();

      /**
       * Draw current buffer as mesh using OpenGL.
       * 
       * @param primitiveType Type of primitive to be drawn.
       * @param count Count of vertices to be drawn (zero means whole buffer)
       * @param off Offset in vertex buffer (number vertices) to be skiped
       * before drawing. (Zero means skip nothing).
       */
      void draw(GLenum primitiveType, u32 count = 0, u32 off = 0);

      /**
       * Draw current buffer using indices as mesh using OpenGL.
       * 
       * @param idx Pointer to indices buffer.
       * @param primitiveType Type of primitive to be drawn.
       * @param count Count of vertices to be drawn (zero means whole buffer)
       * @param off Offset in vertex buffer (number vertices) to be skiped
       * before drawing. (Zero means skip nothing).
       */
      void drawIndexed(const u32* idx, GLenum primitiveType, u32 count = 0, u32 off = 0);




    private:
      /**
       * Is Current vertex buffer using VBO OpenGL extension
       */
      bool usingVBO;

      u32 vboId;

      /**
       * Pointer to potentional data
       */
      void* data;

      /**
       * size of buffer as count of vertices
       */
      u32 size;

      /**
       * Stride between two vertices in array
       */
      u32 stride;

      /**
       * Is this buffer locked?
       */
      bool locked;

      /**
       * Vertex data format
       */
      Type type;

      /**
       * VBO Extension usage flag
       */
      GLenum vboUsage;

      /**
       * VBO Extension access flag
       */
      GLenum vboAccess;


      /**
       * Calcualtes offset in per vertex data 
       * for particular per vertex data type.
       * 
       * @param t Type of particular part of per
       * vertex data.
       * 
       * @return Offside from begining of per vertex data
       * to t.
       */
      /*u32 offset(Type t)
      {

	if (t & Normal)
	  return 0;

	int off = 0;

	if (t & TexMask) {
	  int tex = (t & TexMask) >> TexShift;
					
	  if (type & Normal)
	    off += 3 * sizeof(float);
	  return off + (tex - 1) * 2 * sizeof(float);
					
	}

	if (t == Vertex) {

	  if (type & Normal)
	    off += 3 * sizeof(float);

	  if (type & TexMask)  {
	    int tex = (type & TexMask) >> TexShift;
	    off += tex * sizeof(float) * 2;
	  }
	  return off;
					
	} 
	return 0;
	};*/

	  u32 offset(Type t)
	  {
	     u32 ret = 0;
	     // check for normal
	     if (type & Normal)
	     {
		if (t & Normal)
		{
		   return ret;
		}
		ret += 3 * sizeof(float);
	     }

	     // check for color
	     if (type & Color)
	     {
		if (t & Color)
		{
		   return ret;
		}
		ret += 4 * sizeof(float);
	     }

	     // check for secondary color
	     if (type & SecColor)
	     {
		if (t & SecColor)
		{
		   return ret;
		}
		ret += 4 * sizeof(float);
	     }

	     // check for foog coordinate
	     if (type & FogCoord)
	     {
		if (t & FogCoord)
		{
		   return ret;
		}
		ret += sizeof(float);
	     }

	     // check for texture coordinates
	     if (type & TexCoordMask)
	     {
		u8 typeTexCoordCount = (type & TexCoordMask) >> TexCoordShift;
		u8 tTexCoordCount = (t & TexCoordMask) >> TexCoordShift;
		if (tTexCoordCount > 0)
		{
		   return ret + (tTexCoordCount - 1) * sizeof(float) * 2;
		}
		ret += sizeof(float) * 2 * typeTexCoordCount;
	     }

	     if (t == Vertex)
	     {
		return ret;
	     }

	     return 0;
	  }

      /**
       * Calculates stride of per vertex data.
       * 
       * @param t Format of per vertex data to be computed
       * stride for.
       * 
       * @return Stride of t Format of per vertex data.
       */
      /*u32 calcStride(Type t)
      {
	int stride = 3 * sizeof(float);
	if (t & Normal)
	  stride += sizeof(float) * 3;
	if (t & TexMask) {
	  int tex = (t & TexMask) >> TexShift;
	  stride += tex * sizeof(float) * 2;
	}
	return stride;
	}*/

	  u32 calcStride(Type t)
	  {
	     u32 ret = 0;
	     // check for normal
	     if (t & Normal)
	     {
		ret += 3 * sizeof(float);
	     }

	     // check for color
	     if (t & Color)
	     {
		ret += 4 * sizeof(float);
	     }

	     // check for secondary color
	     if (t & SecColor)
	     {
		ret += 4 * sizeof(float);
	     }

	     // check for foog coordinate
	     if (t & FogCoord)
	     {
		ret += sizeof(float);
	     }

	     // check for texture coordinates
	     if (t & TexCoordMask)
	     {
		u8 tTexCoordCount = (t & TexCoordMask) >> TexCoordShift;
		ret += sizeof(float) * 2 * tTexCoordCount;
	     }

	     ret += sizeof(float) * 3;
	     
	     return ret;
	  }

    };
    
    
  }

}






#endif // __draw_VertexBuffer_Header_File__



